/*************************************************************************
 *                                                                       *
 * This file is part of the DogosFrame distribution.  For full copyright *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 *                                                                       *
 *************************************************************************/

namespace
{

const std::string ONE_TAB_SPACE = "    ";

/*
 * this is a simplified function from LogMessage class.
 * but the LogMessage can not be include in this file,
 * otherwise they will include each other. so this function is required.
 * this is just make the error report have same appearance.
 */
std::string
get_log_info (
    const std::string& info)
{
    std::string result (info);
    result.insert (0, ONE_TAB_SPACE);

    std::string::size_type length = ONE_TAB_SPACE.size();

    std::string::size_type pos = result.find ("\n");
    while (pos != std::string::npos)
    {
        result.insert (pos + 1, ONE_TAB_SPACE);
        pos = result.find ("\n", pos + 1 + length);
    }

    return result;
}

}

namespace DOGOS
{

inline const std::string&
InputDatabase::get_nested_name() const
{
    return d_nested_name;
}


inline std::string
InputDatabase::get_name() const
{
    TBOX_ASSERT (d_database);
    return d_database->getName();
}


inline const Database*
InputDatabase::get_database () const
{
    TBOX_ASSERT (d_database);
    return d_database;
}


inline InputDatabase*
InputDatabase::get_parent_database()
{
    return d_parent_db;
}


inline int
InputDatabase::get_line_number (
    const std::string& key) const
{
    TBOX_ASSERT (d_key_line_number.count (key) > 0);
    return d_key_line_number.find (key)->second;
}


inline int
InputDatabase::get_line_number () const
{
    if (d_is_top_db)
    {
        return 1;
    }
    else
    {
        return d_parent_db->get_line_number (d_database->getName());
    }
}


template<typename T>
void
InputDatabase::check_allowable_enum_value (
    const std::string& key) const
{
    /*
     * note that in this file, do NOT contains any LogMessage's function
     * to avoid include each other.
     */
    if (!this->key_exists(key))
    {
        return;
    }

    this->check_optional_key (key, DataType::STRING, DefaultSwitch::ON, ArraySize::ANY);
    const std::vector<std::string>& string_vector = this->get_string_vector (key);

    for (std::size_t i = 0; i < string_vector.size(); ++i)
    {
        const std::string& string_value = string_vector[i];
        const T enum_value = Utilities::string_to_enum<T>(string_value);
        if (Utilities::is_invalid_value <T> (enum_value))
        {
            std::ostringstream os;

            const int line_number = this->get_line_number (key);
            os << ONE_TAB_SPACE << "in \"" << this->get_file_name() << "\"\nin line " << line_number << '\n';
            os << ONE_TAB_SPACE << "in \"" << d_nested_name << "\"\n";

            os << ONE_TAB_SPACE << "Unrecognized " << key;
            if (string_vector.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = \"" << string_value << "\"\n";

            std::set<std::string> acceptable_values (Utilities::get_acceptable_enum_values<T>());

            if (!acceptable_values.empty())
            {
                os << ONE_TAB_SPACE << "the following values are acceptable:\n";
                os << get_log_info (Utilities::get_rearranged_set<std::string>(acceptable_values, true, 1)) << '\n';

                std::string gauss_value;
                bool has_tip = Utilities::get_similar_string(
                                   string_value,
                                   acceptable_values,
                                   gauss_value,
                                   3);
                if (has_tip)
                {
                    os << '\n';
                    os << ONE_TAB_SPACE << "do you mean \"" << gauss_value << "\" ?" << std::endl;
                }
            }
            TBOX_ERROR(os.str());
        }
    }
}


template<typename T>
T
InputDatabase::get_enum (
    const std::string& key) const
{
    /*
     * note that in this function, function in LogMessage can not
     * be called, otherwise, include each other will cause error.
     */
    this->check_requisite_key (key, DataType::STRING, DefaultSwitch::OFF, ArraySize::ONE);

    return get_enum_with_default (key, Utilities::string_to_enum<T> ("INVALID_VALUE"));
}


template<typename T>
T
InputDatabase::get_enum_with_default (
    const std::string& key,
    const T default_value) const
{
    /*
     * note that in this function, function in LogMessage can not
     * be called, otherwise, include each other will cause error.
     */
    this->check_optional_key (key, DataType::STRING, DefaultSwitch::ON, ArraySize::ONE);
    const std::string default_string_value = Utilities::enum_to_string<T>(default_value);
    const std::string string_value = this->get_string_with_default (key, default_string_value);
    const T enum_value = Utilities::string_to_enum<T>(string_value);
    if (Utilities::is_invalid_value <T> (enum_value))
    {
        std::ostringstream os;
        const int line_number = this->get_line_number (key);
        os << ONE_TAB_SPACE << "in \"" << this->get_file_name() << "\"\nin line " << line_number << '\n';
        os << ONE_TAB_SPACE << "in \"" << d_nested_name << "\"\n";

        os << ONE_TAB_SPACE << "Unrecognized \"" << key << "\" = \"" << string_value << "\"\n";
        os << ONE_TAB_SPACE << "the following values are acceptable:\n";

        std::set<std::string> acceptable_values (Utilities::get_acceptable_enum_values<T>());
        os << get_log_info (Utilities::get_rearranged_set<std::string>(acceptable_values, true, 1)) << '\n';

        std::string gauss_value;
        bool has_tip = Utilities::get_similar_string(
                           string_value,
                           acceptable_values,
                           gauss_value,
                           3);
        if (has_tip)
        {
            os << '\n';
            os << ONE_TAB_SPACE << "do you mean \"" << gauss_value << "\" ?" << std::endl;
        }
        TBOX_ERROR(os.str());
    }

    return enum_value;
}


template<typename T>
T
InputDatabase::get_enum_with_default (
    const std::string& key,
    const T default_value,
    const std::set<std::string>& allowable_items) const
{
    /*
     * note that in this function, function in LogMessage can not
     * be called, otherwise, include each other will cause error.
     */
    this->check_optional_key (key, DataType::STRING, DefaultSwitch::ON, ArraySize::ONE);
    const std::string default_string_value = Utilities::enum_to_string<T>(default_value);
    const std::string string_value = this->get_string_with_default (key, default_string_value);

    if (allowable_items.count (string_value) == 0)
    {
        std::ostringstream os;
        const int line_number = this->get_line_number (key);
        os << ONE_TAB_SPACE << "in \"" << this->get_file_name() << "\"\nin line " << line_number << '\n';
        os << ONE_TAB_SPACE << "in \"" << d_nested_name << "\"\n";

        os << ONE_TAB_SPACE << "Unrecognized \"" << key << "\" = \"" << string_value << "\"\n";
        os << ONE_TAB_SPACE << "the following values are acceptable:\n";

        os << get_log_info (Utilities::get_rearranged_set<std::string>(allowable_items, true, 1)) << '\n';

        std::string gauss_value;
        bool has_tip = Utilities::get_similar_string(
                           string_value,
                           allowable_items,
                           gauss_value,
                           3);
        if (has_tip)
        {
            os << '\n';
            os << ONE_TAB_SPACE << "do you mean \"" << gauss_value << "\" ?" << std::endl;
        }
        TBOX_ERROR(os.str());
    }

    const T enum_value = Utilities::string_to_enum<T>(string_value);
    TBOX_ASSERT (!Utilities::is_invalid_value (enum_value));

    return enum_value;
}


template<typename T>
T
InputDatabase::get_enum_from_subdatabase(
    const std::string& key,
    const std::string& key_in_subdb) const
{
    this->check_requisite_key (key, DataType::DATABASE, DefaultSwitch::OFF, ArraySize::ONE);
    const InputDatabase* sub_db = this->get_database (key);
    sub_db->check_requisite_key (key_in_subdb, DataType::STRING, DefaultSwitch::OFF, ArraySize::ONE);
    return sub_db->get_enum<T>(key_in_subdb);
}


template<typename T>
T
InputDatabase::get_enum_from_subdatabase(
    const std::string& key,
    const std::string& key_in_subdb,
    const T default_value) const
{
    this->check_requisite_key (key, DataType::DATABASE, DefaultSwitch::OFF, ArraySize::ONE);
    const InputDatabase* sub_db = this->get_database (key);
    sub_db->check_optional_key (key_in_subdb, DataType::STRING, DefaultSwitch::ON, ArraySize::ONE);
    return sub_db->get_enum_with_default (key_in_subdb, default_value);
}


template<typename T>
std::vector<T>
InputDatabase::get_enum_vector (
    const std::string& key) const
{
    /*
     * note that in this file, do NOT contains any LogMessage's function
     * to avoid include each other.
     */

    this->check_requisite_key (key, DataType::STRING, DefaultSwitch::OFF, ArraySize::ANY);

    std::vector<std::string> string_vector = this->get_string_vector (key);
    std::vector<T> enum_vector (string_vector.size());

    for (std::size_t i = 0; i < string_vector.size(); ++i)
    {
        const std::string& string_value = string_vector[i];
        const T enum_value = Utilities::string_to_enum<T>(string_value);
        if (Utilities::is_invalid_value<T>(enum_value))
        {
            std::ostringstream os;

            const int line_number = this->get_line_number (key);
            os << ONE_TAB_SPACE << "in \"" << this->get_file_name() << "\"\nin line " << line_number << '\n';
            os << ONE_TAB_SPACE << "in \"" << d_nested_name << "\"\n";

            os << ONE_TAB_SPACE << "Unrecognized \"" << key;
            if (string_vector.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << "\" = \"" << string_value << "\"\n";
            os << ONE_TAB_SPACE << "the following values are acceptable:\n";

            std::set<std::string> acceptable_values (Utilities::get_acceptable_enum_values<T>());
            os << get_log_info (Utilities::get_rearranged_set<std::string>(acceptable_values, true, 1)) << '\n';

            std::string gauss_value;
            bool has_tip = Utilities::get_similar_string(
                               string_value,
                               acceptable_values,
                               gauss_value,
                               3);
            if (has_tip)
            {
                os << '\n';
                os << ONE_TAB_SPACE << "do you mean \"" << gauss_value << "\" ?" << std::endl;
            }
            TBOX_ERROR(os.str());
        }
        enum_vector[i] = enum_value;
    }

    return enum_vector;
}


template<typename T>
void
InputDatabase::show_error_of_unrecognized_enum_item(
    const std::string& key,
    const int num) const
{
    this->check_requisite_key (key, DataType::STRING, DefaultSwitch::OFF, ArraySize::ANY);
    TBOX_ASSERT_GE (num, 0);
    TBOX_ASSERT_LT (num, this->get_string_vector(key).size());

    std::ostringstream os;
    os << get_line_number_message (key) << '\n';
    os << "unrecognized " << key;
    if (this->get_array_size (key) > 1)
    {
        os << "[" << num << "]";
    }

    os << " = \"" << this->get_string_vector (key)[num] << "\"\n";
    os << "the following values are acceptable:\n";

    std::set<std::string> acceptable_values (Utilities::get_acceptable_enum_values<T>());
    os << Utilities::get_rearranged_set<std::string>(acceptable_values, true, 1) << '\n';

    std::string gauss_value;
    bool has_tip = Utilities::get_similar_string(
                       this->get_string_vector (key)[num],
                       acceptable_values,
                       gauss_value,
                       3);
    if (has_tip)
    {
        os << "\ndo you mean \"" << gauss_value << "\" ?" << std::endl;
    }

    TBOX_ERROR (LogMessage::get_log_info (os, ONE_TAB));
}

}
